package gotpsess2

import (
	stdaes "crypto/aes"
	"net"
	"net/http"
	"sieve_admin_server_userpass/lib/config"
	"sieve_admin_server_userpass/lib/crypto/aes"
	"sieve_admin_server_userpass/lib/gotp"
	"sieve_admin_server_userpass/lib/json"
	"sieve_admin_server_userpass/lib/log"
	"strings"
	"sync"
	"time"
)

var (
	fPath    = config.String("lib.gotpsess.path", "/", "")
	fKey     = config.String("lib.gotpsess.key", "gotpsess2", "")
	fExpire  = config.Int64("lib.gotpsess.expire", 0, "")
	fShowErr = config.Bool("lib.gotpsess.showerr", false, "")

	secrets = map[string]string{}
	lock    = sync.RWMutex{}
)

func SetUserSecrets(m map[string]string) {
	lock.Lock()
	defer lock.Unlock()
	secrets = map[string]string{}
	for name, secret := range m {
		if name == "" || secret == "" || len(secret)%stdaes.BlockSize != 0 {
			continue
		}
		secrets[name] = secret
	}
}

func getUser(name string) *User {
	lock.RLock()
	defer lock.RUnlock()
	if s := secrets[name]; s != "" {
		return &User{Name: name, secret: s}
	}
	return nil
}

type User struct {
	Name   string
	secret string
	Value  string
}

type sessData struct {
	T int64  `json:"t,omitempty"`
	H string `json:"h,omitempty"`
	V string `json:"v,omitempty"`
}

func logErr(err error) {
	if *fShowErr {
		log.Err(err)
	}
}

func (u *User) update(w http.ResponseWriter, sess *sessData) error {
	j, err := json.Marshal(sess)
	if err != nil {
		return err
	}
	v, err := aes.EncryptToString([]byte(u.secret), j)
	if err != nil {
		return err
	}
	now := time.Now()
	exp := now.AddDate(1, 0, 0)
	http.SetCookie(w, &http.Cookie{
		Path:     *fPath,
		Name:     *fKey,
		Value:    u.Name + ":" + v,
		HttpOnly: true,
		Expires:  exp,
		MaxAge:   int(exp.Sub(now).Seconds()),
	})
	return nil
}

func LoginSet(w http.ResponseWriter, r *http.Request, user, pwd string, val string) bool {
	u := getUser(user)
	if u == nil {
		return false
	}
	otp, err := gotp.NewGoogleAuth(gotp.GenerateSecret([]byte(u.secret)))
	if err != nil {
		logErr(err)
		return false
	}
	if pwd != otp.GenerateByTime() {
		return false
	}
	if err := u.update(w, &sessData{T: time.Now().Unix(), H: getHost(r.Host), V: val}); err != nil {
		logErr(err)
		return false
	}
	return true
}

func Login(w http.ResponseWriter, r *http.Request, user, pwd string) bool {
	return LoginSet(w, r, user, pwd, "")
}

func Logout(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Path:   *fPath,
		Name:   *fKey,
		MaxAge: -1,
	})
}

func GetUser(w http.ResponseWriter, r *http.Request) *User {
	ck, err := r.Cookie(*fKey)
	if err != nil {
		logErr(err)
		return nil
	}
	if ck.Value == "" {
		return nil
	}
	ss := strings.SplitN(ck.Value, ":", 2)
	if len(ss) != 2 {
		return nil
	}
	u := getUser(ss[0])
	if u == nil {
		return nil
	}
	b, err := aes.DecryptString([]byte(u.secret), ss[1])
	if err != nil {
		logErr(err)
		return nil
	}

	sess := &sessData{}
	if err := json.Unmarshal(b, &sess); err != nil {
		logErr(err)
		return nil
	}
	if sess.H != getHost(r.Host) {
		return nil
	}
	now := time.Now().Unix()
	if *fExpire > 0 && sess.T+*fExpire < now {
		return nil
	}

	sess.T = now
	u.Value = sess.V
	if err := u.update(w, sess); err != nil {
		logErr(err)
		return nil
	}
	return u
}

func Check(w http.ResponseWriter, r *http.Request) bool {
	return GetUser(w, r) != nil
}

func getHost(s string) string {
	h, _, _ := net.SplitHostPort(s)
	return h
}
